Introduction
In this tutorial you will be introduced to r5r: an interface to the R5 routing engine developed by
Conveyal. R5 allows you to do “rapid, realistic” routing on multimodal
networks. It’s very flexible, enabling intermodal analyses and has the
key strength of retaining the full detail of the public transport
timetable. If you’re more familiar with python, you can consider using
r5py instead - although it
may not have all of the same functionality.
The tutorial consists of the following main steps:
- Installing
r5r
- GTFS preparation
- Building a
r5r model
- Preparing origin-destination data
- Calculating and exporting a TTM
In addition to r5r, you will be exposed to several other useful R
packages: dplyr (for working
with data tables), sf (for
working with geodata), and tidytransit (for
working with GTFS data). These are all very powerful packages that can
make your life a lot easier if you learn how to use them! You’re
encouraged to look into the documentation of these packages for more
information.
Tip: you can modify this file and use it as a template to
help you complete your project ;)
Installing r5r
All the packages listed below (including r5r) can be
installed by clicking “packages > install” on the sidebar and
searching for the package name or by executing the following command:
install.packages("package_name_here")

Since R5 (the routing engine itself) is written in java, we need to
install the Java Development Kit (JDK) to get it to work. Specifically
we need version 21. You can install it here: https://www.oracle.com/de/java/technologies/downloads/#java21
Once that’s done, we can load all the packages we’ll be using
today.
library(tidyverse)
library(sf)
library(tidytransit)
options(java.parameters = '-Xmx4G') # we need to allocate RAM for r5r to work. You can increase this depending on your system resources.
library(r5r)
For the code to run correctly, you need to set the “working
directory”. This should be the repository (folder) you downloaded /
cloned from github.
setwd("C:\\you_need_to_update_this_path\\ilutm-r5r-tutorial\\")
Error in setwd("C:\\you_need_to_update_this_path\\ilutm-r5r-tutorial\\") :
cannot change working directory
GTFS preparation
- Download data from: https://gtfs.de/ - pick “Deutschland gesamt”
- Move the zip file into the
/raw_data/ folder
- Using the full feed for all of Germany will require a lot of
resources. Since we’ll be analyzing a single city - lets filter the feed
so that it is smaller. We’ll be using
tidytransit for
this.
- Let’s first load in geodata representing the area we’ll be trimming
to (in this case - Munich). We’ll use
st_read() from the
sf package for this. It can handle all typical geospatial
file types (.shp,.gpkg,etc.)
area <- st_read("./raw_data/munich_admin.gpkg")
Reading layer `boundary_administrative_munich' from data source
`C:\Users\barte\Documents\R\ilutm-r5r-tutorial\raw_data\munich_admin.gpkg' using driver `GPKG'
Simple feature collection with 25 features and 2 fields
Geometry type: MULTIPOLYGON
Dimension: XY
Bounding box: xmin: 11.36078 ymin: 48.06162 xmax: 11.72291 ymax: 48.24812
Geodetic CRS: WGS 84
ggplot()+
geom_sf(data = area)

- It’s good practice to buffer our study area to avoid any edge
effects. Let’s buffer our area by 5 km. NOTE: in this tutorial we’re
just going to be looking at accessibility within Munich. If your
destinations extend beyond the border of your study area, you may need
to buffer your study area significantly! (>>5 km)
- We’ve seen how to do this in QGIS, let’s use
sf this
time
area_buffered <- st_transform(area,crs = 25832) # switch crs to projected (ETRS89 / UTM zone 32N)
area_buffered <- st_union(area_buffered) # join geometries
area_buffered <- st_buffer(area_buffered, 5000) # buffer by 5km
area_buffered <- st_transform(area_buffered,crs = 4326) #switch back to WGS84 for filtering the GTFS feed
ggplot()+
geom_sf(data = area_buffered)+
geom_sf(data = area)

- Let’s load in the GTFS feed we downloaded and trim it.
gtfs_raw <- read_gtfs("./raw_data/latest.zip",encoding = "UTF-8") # path should point to the GTFS feed you downloaded
|--------------------------------------------------|
|==================================================|
gtfs_filtered <- filter_feed_by_area(gtfs_raw,st_bbox(area_buffered))
- We can see that the number of stops in the feed has been
significantly reduced.
- Let’s save the filtered feed. R5 will use this to build a network,
you can also use it for other public transport analyses (refer to
previous tutorials). Tip:
tidytransit has a lot of great
functionality for this!
write_gtfs(gtfs_filtered,"./r5r_model/gtfs.zip")
Building a r5r model
- Before running any more code we need to supply R5 the data it needs
to build the network.
- So far we have prepared the GTFS data, but we also need a street
network.
- R5 uses an extract of OSM data to build the street network. To do
this, it needs to be provided with a
.pbf file. This can be
found on Geofabrik or https://slice.openstreetmap.us/.
- OSM by the slice is recommended because it has a cool name and
because you can select the exact area you’re interested in (reducing
network size, improving performance)
- Just as when we were trimming the GTFS feed, extract an area larger
than the study area

Save the .pbf file to the /r5r_model/
folder. The folder should look like this: 
We can now tell r5 to build the network.
Tip: if you are sure you aren’t modifying the GTFS or
.pbf data any further, you can turn
overwrite = F to avoid rebuilding the model
(faster)
r5r_core <- setup_r5(data_path = "./r5r_model", verbose = FALSE,overwrite = T)
Preparing origin-destination data
- In this tutorial we’ll use public transport stops as our origins and
destinations.
- As we’ve seen in a previous tutorial, we can get this information
directly from the GTFS feed. Let’s do it in R instead of QGIS this
time
- As we discussed in lecture, a single “stop” may be represented by
multiple in a GTFS feed (e.g., a bus stop on both sides of the
street).
- We can take some steps to simplify this. We will group all stops
that share the same value for
parent_station and then keep
a single coordinate by taking the average. Be aware that solving this in
a line of code is often too good to be true. GTFS data often contains
mistakes, and may require more careful cleaning!
stops <- summarize(group_by(gtfs_filtered$stops, parent_station),
stop_lat = mean(stop_lat),
stop_lon = mean(stop_lon),
parent_station_name = first(stop_name))
stops
- let’s convert the table to geodata and filter it down to our study
area.
stops <- st_as_sf(stops,coords = c("stop_lon","stop_lat"),crs = 4326,remove = F)
stops <-st_filter(stops,area) #apply spatial filter
stops
Simple feature collection with 1125 features and 4 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 11.38896 ymin: 48.06913 xmax: 11.71527 ymax: 48.22
Geodetic CRS: WGS 84

- Finally, let’s format our data. The origins and destinations should
have an
id column. Let’s add a new column called
id, and set the values to equal
parent_station.
stops <- mutate(stops, id = parent_station)
stops
Simple feature collection with 1125 features and 5 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 11.38896 ymin: 48.06913 xmax: 11.71527 ymax: 48.22
Geodetic CRS: WGS 84
Calculating and exporting a TTM
The main functionality of r5r is the ability to
calculate a travel time matrix. Knowing the travel time between pairs of
points is useful on its own. It can also be used if you want more
control over your accessibility calculations. For example, if you
calculate a travel time matrix between pairs of public transport stops,
you can calculate the access (origin –> first public transport stop)
and egress (last public transport stop –> destination) legs of the
trip independently. In other words, combining the output of r5r with the
QGIS-based accessibility calculations we did in earlier tutorials.
Let’s give it a shot:
tip: there are many other settings you can change, you’re encouraged
to read the documentation of r5r to see what is possible!
The documentation has many
nice examples!
stops: our origins are the public transport stops
destinations: our destinations are the public transport
stops
mode: the primary mode is public transport (which
includes walking for access and transfers)
mode_egress: the egress mode is walking
departure_datetime: start time
time_window: A travel time matrix will be calculated
for each minute, for time_window minutes after the
departure time. This allows for us to account for the variability in
travel times. Imagine we ran our analysis only at 8:00 and we just
missed a departure of an S-Bahn at 7:59, the travel time that we’d
calculate would be severely penalized by an excessive wait time.
Calculating travel times over a period of time lets us mitigate this.
This parameter can be reduced (default is 10) as it can be
computationally expensive. It’s increasingly important in suburban and
rural areas where service frequencies are low.
percentiles: this corresponds to the
time_window. a value of 50 will report the median travel
time. We can specify multiple percentiles by using a vector
c()
max_walk_time: max time for access, egress, and
transfers by walking
walk_speed: (kph)
max_ride: maximum # of transfers
TTM<-
travel_time_matrix(
r5r_core,
origins = stops,
destinations = stops,
mode = c("TRANSIT"),
mode_egress = "WALK",
departure_datetime = as.POSIXct("26-05-2025 9:00:00", format = "%d-%m-%Y %H:%M:%S"), #its important that the date and time is within your GTFS feed
time_window = 10L, #
percentiles = c(10,50),
max_walk_time = 10,
max_trip_duration = 30L,
walk_speed = 4,
max_rides = 3,
)
TTM
The impact of the time_window parameter can be quite
significant on an analysis! In this example we calculated:
- the 10th percentile travel time (meaning 10% of trips are at least
this fast)
- the 50th percentile travel time (meaning 50% of trips are at least
this fast)
The proportion of OD pairs that had a 50th percentile travel time
> 30 min and a 10th percentile travel time <= 30 min:
TTM <- mutate(TTM,tt_diff = travel_time_p50 - travel_time_p10)
print(round(sum(is.na(TTM$tt_diff)) / length(TTM$tt_diff),1))
[1] 0.3
For those with a 10th and 50th percentile travel time <= 30 min,
the distribution of the difference between the two is as follows:
TTM$tt_diff%>%na.omit()%>%summary()
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.000 2.000 3.000 3.127 4.000 21.000
TTM<-TTM%>%select(-tt_diff)
Let’s get the travel time matrix ready for an export. We will
restructure the data so that the geometry of the destination stops
(to_id) is added to each row. We will also add the name of
the origin station so that the data is easier to work with. This is a
similar operation to a VLOOKUP in excel. We use the from_id
and to_id fields as they represent the
parent_station.
For more guidance on dplyr functions (like
left_join()) check out this cheat
sheet
# join the geometry of the destination station
geometry_to_join <- select(stops,id) # drop unnecessary cols
geometry_to_join <- rename(geometry_to_join,to_id = "id") # rename "id" to "to_id" so that the data is joined as expected
TTM <-
left_join(TTM,
geometry_to_join,
by = "to_id")
# join the station name of the origin station
names_to_join <- select(stops,parent_station_name,id) # drop unnecessary cols
names_to_join <- st_drop_geometry(names_to_join) # drop geometry
names_to_join <- rename(names_to_join,from_id = "id") # rename "id" to "from_id" so that the data is joined as expected
TTM <-
left_join(TTM,
names_to_join,
by = "from_id")
TTM <- st_as_sf(TTM)
TTM
Simple feature collection with 294664 features and 5 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 11.38896 ymin: 48.06913 xmax: 11.71527 ymax: 48.22
Geodetic CRS: WGS 84
First 10 features:
from_id to_id travel_time_p10 travel_time_p50 parent_station_name geometry
1 100060 100060 0 0 Josef-Lang-Straße POINT (11.47202 48.15011)
2 100060 10157 29 NA Josef-Lang-Straße POINT (11.46016 48.18474)
3 100060 105596 28 NA Josef-Lang-Straße POINT (11.48913 48.18399)
4 100060 109574 23 27 Josef-Lang-Straße POINT (11.47472 48.12805)
5 100060 11520 25 27 Josef-Lang-Straße POINT (11.54544 48.14428)
6 100060 128167 22 26 Josef-Lang-Straße POINT (11.46642 48.19177)
7 100060 131549 21 25 Josef-Lang-Straße POINT (11.44335 48.16361)
8 100060 131638 21 25 Josef-Lang-Straße POINT (11.47278 48.17485)
9 100060 132365 19 23 Josef-Lang-Straße POINT (11.4531 48.14563)
10 100060 1335 27 NA Josef-Lang-Straße POINT (11.45716 48.20257)
This data format is easier to interpret if we look at a single stop.
For example, lets find all stops that can be reached from
Theresienstraße within a median travel time of <= 20 minutes.
ts_stops_20 <- filter(TTM,parent_station_name == "Theresienstraße", travel_time_p50 <= 20)
ggplot()+
geom_sf(data = area)+
geom_sf(data = ts_stops_20, color = "lightblue")+
geom_sf(data = filter(stops,parent_station_name == "Theresienstraße"), color = "blue")

Let’s export this (& the stops) so that we can work with it in
QGIS.
st_write(TTM,"./output/TTM.gpkg",append = F)
Deleting layer `TTM' using driver `GPKG'
Writing layer `TTM' to data source `./output/TTM.gpkg' using driver `GPKG'
Writing 294664 features with 5 fields and geometry type Point.
st_write(stops, "./output/stops.gpkg", append = F)
Deleting layer `stops' using driver `GPKG'
Writing layer `stops' to data source `./output/stops.gpkg' using driver `GPKG'
Writing 1125 features with 5 fields and geometry type Point.
We can achieve the same result as above by doing the following:

Then, by using what you’ve learned in previous tutorials, you can
create service areas:

We’ve just scratched the surface of what you can do with
r5r and tidytransit. Consider taking a look
through the documentation for some inspiration for your project!
LS0tDQp0aXRsZTogIlI1UiBUdXRvcmlhbCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9IA0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKSANCmBgYA0KDQpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KYGBgDQoNCg0KIyBJbnRyb2R1Y3Rpb24NCg0KDQpJbiB0aGlzIHR1dG9yaWFsIHlvdSB3aWxsIGJlIGludHJvZHVjZWQgdG8gW3I1cl0oaHR0cHM6Ly9pcGVhZ2l0LmdpdGh1Yi5pby9yNXIvKTogYW4gaW50ZXJmYWNlIHRvIHRoZSBbUjVdKGh0dHBzOi8vZ2l0aHViLmNvbS9jb252ZXlhbC9yNSkgcm91dGluZyBlbmdpbmUgZGV2ZWxvcGVkIGJ5IENvbnZleWFsLg0KUjUgYWxsb3dzIHlvdSB0byBkbyAicmFwaWQsIHJlYWxpc3RpYyIgcm91dGluZyBvbiBtdWx0aW1vZGFsIG5ldHdvcmtzLiBJdCdzIHZlcnkgZmxleGlibGUsIGVuYWJsaW5nDQppbnRlcm1vZGFsIGFuYWx5c2VzIGFuZCBoYXMgdGhlIGtleSBzdHJlbmd0aCBvZiByZXRhaW5pbmcgdGhlIGZ1bGwgZGV0YWlsIG9mIHRoZSBwdWJsaWMgdHJhbnNwb3J0IHRpbWV0YWJsZS4NCklmIHlvdSdyZSBtb3JlIGZhbWlsaWFyIHdpdGggcHl0aG9uLCB5b3UgY2FuIGNvbnNpZGVyIHVzaW5nIFtyNXB5XShodHRwczovL2dpdGh1Yi5jb20vcjVweS9yNXB5KSBpbnN0ZWFkIC0gYWx0aG91Z2ggaXQgbWF5IG5vdCBoYXZlIGFsbCBvZiB0aGUgc2FtZSBmdW5jdGlvbmFsaXR5Lg0KDQpUaGUgdHV0b3JpYWwgY29uc2lzdHMgb2YgdGhlIGZvbGxvd2luZyBtYWluIHN0ZXBzOg0KDQoxLiBJbnN0YWxsaW5nIGByNXJgDQoyLiBHVEZTIHByZXBhcmF0aW9uDQozLiBCdWlsZGluZyBhIGByNXJgIG1vZGVsDQozLiBQcmVwYXJpbmcgb3JpZ2luLWRlc3RpbmF0aW9uIGRhdGENCjQuIENhbGN1bGF0aW5nIGFuZCBleHBvcnRpbmcgYSBUVE0NCg0KDQpJbiBhZGRpdGlvbiB0byByNXIsIHlvdSB3aWxsIGJlIGV4cG9zZWQgdG8gc2V2ZXJhbCBvdGhlciB1c2VmdWwgUiBwYWNrYWdlczogW2RwbHlyXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvKSAoZm9yIHdvcmtpbmcgd2l0aCBkYXRhIHRhYmxlcyksIFtzZl0oaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL3NmLykgKGZvciB3b3JraW5nIHdpdGggZ2VvZGF0YSksIGFuZCBbdGlkeXRyYW5zaXRdKGh0dHBzOi8vZ2l0aHViLmNvbS9yLXRyYW5zaXQvdGlkeXRyYW5zaXQpIChmb3Igd29ya2luZyB3aXRoIEdURlMgZGF0YSkuIFRoZXNlIGFyZSBhbGwgdmVyeQ0KcG93ZXJmdWwgcGFja2FnZXMgdGhhdCBjYW4gbWFrZSB5b3VyIGxpZmUgYSBsb3QgZWFzaWVyIGlmIHlvdSBsZWFybiBob3cgdG8gdXNlIHRoZW0hIFlvdSdyZSBlbmNvdXJhZ2VkIA0KdG8gbG9vayBpbnRvIHRoZSBkb2N1bWVudGF0aW9uIG9mIHRoZXNlIHBhY2thZ2VzIGZvciBtb3JlIGluZm9ybWF0aW9uLg0KDQoqKlRpcDogeW91IGNhbiBtb2RpZnkgdGhpcyBmaWxlIGFuZCB1c2UgaXQgYXMgYSB0ZW1wbGF0ZSB0byBoZWxwIHlvdSBjb21wbGV0ZSB5b3VyIHByb2plY3QgOykqKg0KDQoNCiMgSW5zdGFsbGluZyBgcjVyYA0KDQpBbGwgdGhlIHBhY2thZ2VzIGxpc3RlZCBiZWxvdyAoaW5jbHVkaW5nIGByNXJgKSBjYW4gYmUgaW5zdGFsbGVkIGJ5IGNsaWNraW5nICJwYWNrYWdlcyA+IGluc3RhbGwiDQpvbiB0aGUgc2lkZWJhciBhbmQgc2VhcmNoaW5nIGZvciB0aGUgcGFja2FnZSBuYW1lIG9yIGJ5IGV4ZWN1dGluZyB0aGUgZm9sbG93aW5nIGNvbW1hbmQ6IGBpbnN0YWxsLnBhY2thZ2VzKCJwYWNrYWdlX25hbWVfaGVyZSIpYA0KDQohW10oLi9pbWcvaW5zdGFsbF9wYWNrYWdlcy5wbmcpe3dpZHRoPTMwMHB4fQ0KDQoNClNpbmNlIFI1ICh0aGUgcm91dGluZyBlbmdpbmUgaXRzZWxmKSBpcyB3cml0dGVuIGluIGphdmEsIHdlIG5lZWQgdG8gaW5zdGFsbCB0aGUgSmF2YSBEZXZlbG9wbWVudCBLaXQgKEpESykNCnRvIGdldCBpdCB0byB3b3JrLiBTcGVjaWZpY2FsbHkgd2UgbmVlZCB2ZXJzaW9uICoqMjEqKi4gWW91IGNhbiBpbnN0YWxsIGl0IGhlcmU6IGh0dHBzOi8vd3d3Lm9yYWNsZS5jb20vZGUvamF2YS90ZWNobm9sb2dpZXMvZG93bmxvYWRzLyNqYXZhMjENCg0KDQpPbmNlIHRoYXQncyBkb25lLCB3ZSBjYW4gbG9hZCBhbGwgdGhlIHBhY2thZ2VzIHdlJ2xsIGJlIHVzaW5nIHRvZGF5Lg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KHRpZHl0cmFuc2l0KQ0KDQpvcHRpb25zKGphdmEucGFyYW1ldGVycyA9ICctWG14NEcnKSAjIHdlIG5lZWQgdG8gYWxsb2NhdGUgUkFNIGZvciByNXIgdG8gd29yay4gWW91IGNhbiBpbmNyZWFzZSB0aGlzIGRlcGVuZGluZyBvbiB5b3VyIHN5c3RlbSByZXNvdXJjZXMuDQpsaWJyYXJ5KHI1cikNCg0KYGBgDQoNCkZvciB0aGUgY29kZSB0byBydW4gY29ycmVjdGx5LCB5b3UgbmVlZCB0byBzZXQgdGhlICJ3b3JraW5nIGRpcmVjdG9yeSIuIFRoaXMgc2hvdWxkIGJlIHRoZSByZXBvc2l0b3J5IChmb2xkZXIpIHlvdSBkb3dubG9hZGVkIC8gY2xvbmVkIGZyb20gZ2l0aHViLg0KYGBge3J9DQpzZXR3ZCgiQzpcXHlvdV9uZWVkX3RvX3VwZGF0ZV90aGlzX3BhdGhcXGlsdXRtLXI1ci10dXRvcmlhbFxcIikNCmBgYA0KDQoNCiMgR1RGUyBwcmVwYXJhdGlvbg0KDQotIERvd25sb2FkIGRhdGEgZnJvbTogaHR0cHM6Ly9ndGZzLmRlLyAtIHBpY2sgIkRldXRzY2hsYW5kIGdlc2FtdCINCi0gTW92ZSB0aGUgemlwIGZpbGUgaW50byB0aGUgYC9yYXdfZGF0YS9gIGZvbGRlcg0KLSBVc2luZyB0aGUgZnVsbCBmZWVkIGZvciBhbGwgb2YgR2VybWFueSB3aWxsIHJlcXVpcmUgYSBsb3Qgb2YgcmVzb3VyY2VzLiBTaW5jZSB3ZSdsbCBiZSANCmFuYWx5emluZyBhIHNpbmdsZSBjaXR5IC0gbGV0cyBmaWx0ZXIgdGhlIGZlZWQgc28gdGhhdCBpdCBpcyBzbWFsbGVyLiBXZSdsbCBiZSB1c2luZyBgdGlkeXRyYW5zaXRgIGZvciB0aGlzLg0KLSBMZXQncyBmaXJzdCBsb2FkIGluIGdlb2RhdGEgcmVwcmVzZW50aW5nIHRoZSBhcmVhIHdlJ2xsIGJlIHRyaW1taW5nIHRvIChpbiB0aGlzIGNhc2UgLSBNdW5pY2gpLiBXZSdsbCB1c2UgYHN0X3JlYWQoKWAgZnJvbSB0aGUgYHNmYCBwYWNrYWdlIGZvciB0aGlzLiBJdCBjYW4gaGFuZGxlIGFsbCB0eXBpY2FsIGdlb3NwYXRpYWwgZmlsZSB0eXBlcyAoYC5zaHBgLGAuZ3BrZ2AsZXRjLikNCg0KYGBge3J9DQphcmVhIDwtIHN0X3JlYWQoIi4vcmF3X2RhdGEvbXVuaWNoX2FkbWluLmdwa2ciKQ0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhICA9IGFyZWEpDQoNCmBgYA0KLSBJdCdzIGdvb2QgcHJhY3RpY2UgdG8gYnVmZmVyIG91ciBzdHVkeSBhcmVhIHRvIGF2b2lkIGFueSBlZGdlIGVmZmVjdHMuIExldCdzIGJ1ZmZlciBvdXIgYXJlYSBieSA1IGttLiBOT1RFOiBpbiB0aGlzIHR1dG9yaWFsIHdlJ3JlIGp1c3QgZ29pbmcgdG8gYmUgbG9va2luZyBhdCBhY2Nlc3NpYmlsaXR5IHdpdGhpbiBNdW5pY2guIElmIHlvdXIgZGVzdGluYXRpb25zIGV4dGVuZCBiZXlvbmQgdGhlIGJvcmRlciBvZiB5b3VyIHN0dWR5IGFyZWEsIHlvdSBtYXkgbmVlZCB0byBidWZmZXIgeW91ciBzdHVkeSBhcmVhIHNpZ25pZmljYW50bHkhICg+PjUga20pDQotIFdlJ3ZlIHNlZW4gaG93IHRvIGRvIHRoaXMgaW4gUUdJUywgbGV0J3MgdXNlIGBzZmAgdGhpcyB0aW1lDQoNCmBgYHtyfQ0KYXJlYV9idWZmZXJlZCA8LSBzdF90cmFuc2Zvcm0oYXJlYSxjcnMgPSAyNTgzMikgIyBzd2l0Y2ggY3JzIHRvIHByb2plY3RlZCAoRVRSUzg5IC8gVVRNIHpvbmUgMzJOKQ0KDQphcmVhX2J1ZmZlcmVkIDwtIHN0X3VuaW9uKGFyZWFfYnVmZmVyZWQpICMgam9pbiBnZW9tZXRyaWVzIA0KDQphcmVhX2J1ZmZlcmVkIDwtIHN0X2J1ZmZlcihhcmVhX2J1ZmZlcmVkLCA1MDAwKSAjIGJ1ZmZlciBieSA1a20NCg0KYXJlYV9idWZmZXJlZCA8LSBzdF90cmFuc2Zvcm0oYXJlYV9idWZmZXJlZCxjcnMgPSA0MzI2KSAjc3dpdGNoIGJhY2sgdG8gV0dTODQgZm9yIGZpbHRlcmluZyB0aGUgR1RGUyBmZWVkDQoNCg0KZ2dwbG90KCkrDQogIGdlb21fc2YoZGF0YSA9IGFyZWFfYnVmZmVyZWQpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKQ0KYGBgDQotIExldCdzIGxvYWQgaW4gdGhlIEdURlMgZmVlZCB3ZSBkb3dubG9hZGVkIGFuZCB0cmltIGl0LiANCmBgYHtyfQ0KDQpndGZzX3JhdyA8LSByZWFkX2d0ZnMoIi4vcmF3X2RhdGEvbGF0ZXN0LnppcCIsZW5jb2RpbmcgPSAiVVRGLTgiKSAjIHBhdGggc2hvdWxkIHBvaW50IHRvIHRoZSBHVEZTIGZlZWQgeW91IGRvd25sb2FkZWQNCg0KZ3Rmc19maWx0ZXJlZCA8LSBmaWx0ZXJfZmVlZF9ieV9hcmVhKGd0ZnNfcmF3LHN0X2Jib3goYXJlYV9idWZmZXJlZCkpDQoNCmBgYA0KDQotIFdlIGNhbiBzZWUgdGhhdCB0aGUgbnVtYmVyIG9mIHN0b3BzIGluIHRoZSBmZWVkIGhhcyBiZWVuIHNpZ25pZmljYW50bHkgcmVkdWNlZC4NCmBgYHtyIGVjaG89RkFMU0V9DQoNCnRyaWJibGUofm5fc3RvcHNfcmF3LCB+bl9zdG9wc19maWx0ZXJlZCwNCiAgICAgICAgbnJvdyhndGZzX3JhdyRzdG9wcyksIG5yb3coZ3Rmc19maWx0ZXJlZCRzdG9wcykpDQpgYGANCg0KDQotIExldCdzIHNhdmUgdGhlIGZpbHRlcmVkIGZlZWQuIFI1IHdpbGwgdXNlIHRoaXMgdG8gYnVpbGQgYSBuZXR3b3JrLCB5b3UgY2FuIGFsc28gdXNlIGl0IGZvciBvdGhlciBwdWJsaWMgdHJhbnNwb3J0IGFuYWx5c2VzIChyZWZlciB0byBwcmV2aW91cyB0dXRvcmlhbHMpLiBUaXA6IGB0aWR5dHJhbnNpdGAgaGFzIGEgbG90IG9mIGdyZWF0IGZ1bmN0aW9uYWxpdHkgZm9yIHRoaXMhDQpgYGB7cn0NCg0Kd3JpdGVfZ3RmcyhndGZzX2ZpbHRlcmVkLCIuL3I1cl9tb2RlbC9ndGZzLnppcCIpDQoNCmBgYA0KDQoNCg0KIyBCdWlsZGluZyBhIGByNXJgIG1vZGVsDQoNCi0gQmVmb3JlIHJ1bm5pbmcgYW55IG1vcmUgY29kZSB3ZSBuZWVkIHRvIHN1cHBseSBSNSB0aGUgZGF0YSBpdCBuZWVkcyB0byBidWlsZCB0aGUgbmV0d29yay4gDQotIFNvIGZhciB3ZSBoYXZlIHByZXBhcmVkIHRoZSBHVEZTIGRhdGEsIGJ1dCB3ZSBhbHNvIG5lZWQgYSBzdHJlZXQgbmV0d29yay4gDQotIFI1IHVzZXMgYW4gZXh0cmFjdCBvZiBPU00gZGF0YSB0byBidWlsZCB0aGUgc3RyZWV0IG5ldHdvcmsuIFRvIGRvIHRoaXMsIGl0IG5lZWRzIHRvIGJlIHByb3ZpZGVkIHdpdGggYSBgLnBiZmAgZmlsZS4gVGhpcyBjYW4gYmUgZm91bmQgb24gW0dlb2ZhYnJpa10oaHR0cDovL2Rvd25sb2FkLmdlb2ZhYnJpay5kZS8pIG9yIGh0dHBzOi8vc2xpY2Uub3BlbnN0cmVldG1hcC51cy8uIA0KLSBPU00gYnkgdGhlIHNsaWNlIGlzIHJlY29tbWVuZGVkIGJlY2F1c2UgaXQgaGFzIGEgY29vbCBuYW1lIGFuZCBiZWNhdXNlIHlvdSBjYW4gc2VsZWN0IHRoZSBleGFjdCBhcmVhIHlvdSdyZSBpbnRlcmVzdGVkIGluIChyZWR1Y2luZyBuZXR3b3JrIHNpemUsIGltcHJvdmluZyBwZXJmb3JtYW5jZSkNCi0gSnVzdCBhcyB3aGVuIHdlIHdlcmUgdHJpbW1pbmcgdGhlIEdURlMgZmVlZCwgZXh0cmFjdCBhbiBhcmVhIGxhcmdlciB0aGFuIHRoZSBzdHVkeSBhcmVhDQoNCiFbXSguL2ltZy9zbGljZS5wbmcpDQoNCi0gU2F2ZSB0aGUgYC5wYmZgIGZpbGUgdG8gdGhlIGAvcjVyX21vZGVsL2AgZm9sZGVyLiBUaGUgZm9sZGVyIHNob3VsZCBsb29rIGxpa2UgdGhpczoNCiFbXSguL2ltZy9yNXJfZm9sZGVyLnBuZykNCg0KLSBXZSBjYW4gbm93IHRlbGwgcjUgdG8gYnVpbGQgdGhlIG5ldHdvcmsuIA0KLSBUaXA6IGlmIHlvdSBhcmUgc3VyZSB5b3UgYXJlbid0IG1vZGlmeWluZyB0aGUgR1RGUyBvciBgLnBiZmAgZGF0YSBhbnkgZnVydGhlciwgeW91IGNhbiB0dXJuIGBvdmVyd3JpdGUgPSBGYCB0byBhdm9pZCByZWJ1aWxkaW5nIHRoZSBtb2RlbCAoZmFzdGVyKQ0KDQpgYGB7cn0NCnI1cl9jb3JlIDwtIHNldHVwX3I1KGRhdGFfcGF0aCA9ICIuL3I1cl9tb2RlbCIsIHZlcmJvc2UgPSBGQUxTRSxvdmVyd3JpdGUgPSBUKQ0KYGBgDQoNCg0KDQojIFByZXBhcmluZyBvcmlnaW4tZGVzdGluYXRpb24gZGF0YQ0KDQotIEluIHRoaXMgdHV0b3JpYWwgd2UnbGwgdXNlIHB1YmxpYyB0cmFuc3BvcnQgc3RvcHMgYXMgb3VyIG9yaWdpbnMgYW5kIGRlc3RpbmF0aW9ucy4gDQotIEFzIHdlJ3ZlIHNlZW4gaW4gYSBwcmV2aW91cyB0dXRvcmlhbCwgd2UgY2FuIGdldCB0aGlzIGluZm9ybWF0aW9uIGRpcmVjdGx5IGZyb20gdGhlIEdURlMgZmVlZC4gTGV0J3MgZG8gaXQgaW4gUiBpbnN0ZWFkIG9mIFFHSVMgdGhpcyB0aW1lDQotIEFzIHdlIGRpc2N1c3NlZCBpbiBsZWN0dXJlLCBhIHNpbmdsZSAic3RvcCIgbWF5IGJlIHJlcHJlc2VudGVkIGJ5IG11bHRpcGxlIGluIGEgR1RGUyBmZWVkIChlLmcuLCBhIGJ1cyBzdG9wIG9uIGJvdGggc2lkZXMgb2YgdGhlIHN0cmVldCkuIA0KLSBXZSBjYW4gdGFrZSBzb21lIHN0ZXBzIHRvIHNpbXBsaWZ5IHRoaXMuIFdlIHdpbGwgZ3JvdXAgYWxsIHN0b3BzIHRoYXQgc2hhcmUgdGhlIHNhbWUgdmFsdWUgZm9yIGBwYXJlbnRfc3RhdGlvbmAgYW5kIHRoZW4ga2VlcCBhIHNpbmdsZSBjb29yZGluYXRlIGJ5IHRha2luZyB0aGUgYXZlcmFnZS4gQmUgYXdhcmUgdGhhdCBzb2x2aW5nIHRoaXMgaW4gYSBsaW5lIG9mIGNvZGUgaXMgb2Z0ZW4gdG9vIGdvb2QgdG8gYmUgdHJ1ZS4gR1RGUyBkYXRhIG9mdGVuIGNvbnRhaW5zIG1pc3Rha2VzLCBhbmQgbWF5IHJlcXVpcmUgbW9yZSBjYXJlZnVsIGNsZWFuaW5nIQ0KDQpgYGB7cn0NCnN0b3BzIDwtIHN1bW1hcml6ZShncm91cF9ieShndGZzX2ZpbHRlcmVkJHN0b3BzLCBwYXJlbnRfc3RhdGlvbiksDQogICAgICAgICAgICAgICAgICAgc3RvcF9sYXQgPSBtZWFuKHN0b3BfbGF0KSwNCiAgICAgICAgICAgICAgICAgICBzdG9wX2xvbiA9IG1lYW4oc3RvcF9sb24pLA0KICAgICAgICAgICAgICAgICAgIHBhcmVudF9zdGF0aW9uX25hbWUgPSBmaXJzdChzdG9wX25hbWUpKSAgDQpzdG9wcw0KYGBgDQoNCi0gbGV0J3MgY29udmVydCB0aGUgdGFibGUgdG8gZ2VvZGF0YSBhbmQgZmlsdGVyIGl0IGRvd24gdG8gb3VyIHN0dWR5IGFyZWEuDQoNCmBgYHtyfQ0Kc3RvcHMgPC0gc3RfYXNfc2Yoc3RvcHMsY29vcmRzID0gYygic3RvcF9sb24iLCJzdG9wX2xhdCIpLGNycyA9IDQzMjYscmVtb3ZlID0gRikNCg0Kc3RvcHMgPC1zdF9maWx0ZXIoc3RvcHMsYXJlYSkgI2FwcGx5IHNwYXRpYWwgZmlsdGVyDQoNCnN0b3BzDQpgYGANCg0KYGBge3IgZWNobz1GQUxTRX0NCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBhcmVhKSsNCiAgZ2VvbV9zZihkYXRhID0gc3RvcHMsIGNvbG9yID0gImJsdWUiKQ0KYGBgDQoNCg0KLSBGaW5hbGx5LCBsZXQncyBmb3JtYXQgb3VyIGRhdGEuIFRoZSBvcmlnaW5zIGFuZCBkZXN0aW5hdGlvbnMgc2hvdWxkIGhhdmUgYW4gYGlkYCBjb2x1bW4uICBMZXQncyBhZGQgYSBuZXcgY29sdW1uIGNhbGxlZCBgaWRgLCBhbmQgc2V0IHRoZSB2YWx1ZXMgdG8gZXF1YWwgYHBhcmVudF9zdGF0aW9uYC4NCg0KDQpgYGB7cn0NCnN0b3BzIDwtIG11dGF0ZShzdG9wcywgaWQgPSBwYXJlbnRfc3RhdGlvbikNCg0Kc3RvcHMNCmBgYA0KDQoNCiMgQ2FsY3VsYXRpbmcgYW5kIGV4cG9ydGluZyBhIFRUTQ0KDQpUaGUgbWFpbiBmdW5jdGlvbmFsaXR5IG9mIGByNXJgIGlzIHRoZSBhYmlsaXR5IHRvIGNhbGN1bGF0ZSBhIHRyYXZlbCB0aW1lIG1hdHJpeC4gS25vd2luZyB0aGUgdHJhdmVsIHRpbWUgYmV0d2VlbiBwYWlycyBvZiBwb2ludHMgaXMgdXNlZnVsIG9uIGl0cyBvd24uIEl0IGNhbiBhbHNvIGJlIHVzZWQgaWYgeW91IHdhbnQgbW9yZSBjb250cm9sIG92ZXIgeW91ciBhY2Nlc3NpYmlsaXR5IGNhbGN1bGF0aW9ucy4gRm9yIGV4YW1wbGUsIGlmIHlvdSBjYWxjdWxhdGUgYSB0cmF2ZWwgdGltZSBtYXRyaXggYmV0d2VlbiBwYWlycyBvZiBwdWJsaWMgdHJhbnNwb3J0IHN0b3BzLCB5b3UgY2FuIGNhbGN1bGF0ZSB0aGUgYWNjZXNzIChvcmlnaW4gLS0+IGZpcnN0IHB1YmxpYyB0cmFuc3BvcnQgc3RvcCkgYW5kIGVncmVzcyAobGFzdCBwdWJsaWMgdHJhbnNwb3J0IHN0b3AgLS0+IGRlc3RpbmF0aW9uKSBsZWdzIG9mIHRoZSB0cmlwIGluZGVwZW5kZW50bHkuIEluIG90aGVyIHdvcmRzLCBjb21iaW5pbmcgdGhlIG91dHB1dCBvZiByNXIgd2l0aCB0aGUgUUdJUy1iYXNlZCBhY2Nlc3NpYmlsaXR5IGNhbGN1bGF0aW9ucyB3ZSBkaWQgaW4gZWFybGllciB0dXRvcmlhbHMuDQoNCkxldCdzIGdpdmUgaXQgYSBzaG90Og0KDQp0aXA6IHRoZXJlIGFyZSBtYW55IG90aGVyIHNldHRpbmdzIHlvdSBjYW4gY2hhbmdlLCB5b3UncmUgZW5jb3VyYWdlZCB0byByZWFkIHRoZSBkb2N1bWVudGF0aW9uIG9mIGByNXJgIHRvIHNlZSB3aGF0IGlzIHBvc3NpYmxlISBUaGUgW2RvY3VtZW50YXRpb25dKGh0dHBzOi8vaXBlYWdpdC5naXRodWIuaW8vcjVyLykgaGFzIG1hbnkgbmljZSBleGFtcGxlcyENCg0KLSBgc3RvcHNgOiBvdXIgb3JpZ2lucyBhcmUgdGhlIHB1YmxpYyB0cmFuc3BvcnQgc3RvcHMNCi0gYGRlc3RpbmF0aW9uc2A6IG91ciBkZXN0aW5hdGlvbnMgYXJlIHRoZSBwdWJsaWMgdHJhbnNwb3J0IHN0b3BzDQotIGBtb2RlYDogdGhlIHByaW1hcnkgbW9kZSBpcyBwdWJsaWMgdHJhbnNwb3J0ICh3aGljaCBpbmNsdWRlcyB3YWxraW5nIGZvciBhY2Nlc3MgYW5kIHRyYW5zZmVycykNCi0gYG1vZGVfZWdyZXNzYDogdGhlIGVncmVzcyBtb2RlIGlzIHdhbGtpbmcNCi0gYGRlcGFydHVyZV9kYXRldGltZWA6IHN0YXJ0IHRpbWUNCi0gYHRpbWVfd2luZG93YDogQSB0cmF2ZWwgdGltZSBtYXRyaXggd2lsbCBiZSBjYWxjdWxhdGVkIGZvciBlYWNoIG1pbnV0ZSwgZm9yIGB0aW1lX3dpbmRvd2AgbWludXRlcyBhZnRlciB0aGUgZGVwYXJ0dXJlIHRpbWUuIFRoaXMgYWxsb3dzIGZvciB1cyB0byBhY2NvdW50IGZvciB0aGUgdmFyaWFiaWxpdHkgaW4gdHJhdmVsIHRpbWVzLiBJbWFnaW5lIHdlIHJhbiBvdXIgYW5hbHlzaXMgb25seSBhdCA4OjAwIGFuZCB3ZSBqdXN0IG1pc3NlZCBhIGRlcGFydHVyZSBvZiBhbiBTLUJhaG4gYXQgNzo1OSwgdGhlIHRyYXZlbCB0aW1lIHRoYXQgd2UnZCBjYWxjdWxhdGUgd291bGQgYmUgc2V2ZXJlbHkgcGVuYWxpemVkIGJ5IGFuIGV4Y2Vzc2l2ZSB3YWl0IHRpbWUuIENhbGN1bGF0aW5nIHRyYXZlbCB0aW1lcyBvdmVyIGEgcGVyaW9kIG9mIHRpbWUgbGV0cyB1cyBtaXRpZ2F0ZSB0aGlzLiBUaGlzIHBhcmFtZXRlciBjYW4gYmUgcmVkdWNlZCAoZGVmYXVsdCBpcyAxMCkgYXMgaXQgY2FuIGJlIGNvbXB1dGF0aW9uYWxseSBleHBlbnNpdmUuIEl0J3MgaW5jcmVhc2luZ2x5IGltcG9ydGFudCBpbiBzdWJ1cmJhbiBhbmQgcnVyYWwgYXJlYXMgd2hlcmUgc2VydmljZSBmcmVxdWVuY2llcyBhcmUgbG93Lg0KLSBgcGVyY2VudGlsZXNgOiB0aGlzIGNvcnJlc3BvbmRzIHRvIHRoZSBgdGltZV93aW5kb3dgLiBhIHZhbHVlIG9mIDUwIHdpbGwgcmVwb3J0IHRoZSBtZWRpYW4gdHJhdmVsIHRpbWUuIFdlIGNhbiBzcGVjaWZ5IG11bHRpcGxlIHBlcmNlbnRpbGVzIGJ5IHVzaW5nIGEgdmVjdG9yIGBjKClgDQotIGBtYXhfd2Fsa190aW1lYDogbWF4IHRpbWUgZm9yIGFjY2VzcywgZWdyZXNzLCBhbmQgdHJhbnNmZXJzIGJ5IHdhbGtpbmcNCi0gYHdhbGtfc3BlZWRgOiAoa3BoKQ0KLSBgbWF4X3JpZGVgOiBtYXhpbXVtICMgb2YgdHJhbnNmZXJzDQoNCg0KDQpgYGB7cn0NCg0KVFRNPC0NCnRyYXZlbF90aW1lX21hdHJpeCgNCiAgcjVyX2NvcmUsDQogIG9yaWdpbnMgPSBzdG9wcywgDQogIGRlc3RpbmF0aW9ucyA9IHN0b3BzLCANCiAgbW9kZSA9IGMoIlRSQU5TSVQiKSwgIA0KICBtb2RlX2VncmVzcyA9ICJXQUxLIiwgDQogIGRlcGFydHVyZV9kYXRldGltZSA9IGFzLlBPU0lYY3QoIjI2LTA1LTIwMjUgOTowMDowMCIsIGZvcm1hdCA9ICIlZC0lbS0lWSAlSDolTTolUyIpLCAjaXRzIGltcG9ydGFudCB0aGF0IHRoZSBkYXRlIGFuZCB0aW1lIGlzIHdpdGhpbiB5b3VyIEdURlMgZmVlZA0KICB0aW1lX3dpbmRvdyA9IDEwTCwgIyANCiAgcGVyY2VudGlsZXMgPSBjKDEwLDUwKSwNCiAgbWF4X3dhbGtfdGltZSA9IDEwLA0KICBtYXhfdHJpcF9kdXJhdGlvbiA9IDMwTCwNCiAgd2Fsa19zcGVlZCA9IDQsDQogIG1heF9yaWRlcyA9IDMsDQopDQoNClRUTQ0KYGBgDQoNClRoZSBpbXBhY3Qgb2YgdGhlIGB0aW1lX3dpbmRvd2AgcGFyYW1ldGVyIGNhbiBiZSBxdWl0ZSBzaWduaWZpY2FudCBvbiBhbiBhbmFseXNpcyEgSW4gdGhpcyBleGFtcGxlIHdlIGNhbGN1bGF0ZWQ6DQoNCi0gdGhlIDEwdGggcGVyY2VudGlsZSB0cmF2ZWwgdGltZSAobWVhbmluZyAxMCUgb2YgdHJpcHMgYXJlIGF0IGxlYXN0IHRoaXMgZmFzdCkgDQotIHRoZSA1MHRoIHBlcmNlbnRpbGUgdHJhdmVsIHRpbWUgKG1lYW5pbmcgNTAlIG9mIHRyaXBzIGFyZSBhdCBsZWFzdCB0aGlzIGZhc3QpDQoNClRoZSBwcm9wb3J0aW9uIG9mIE9EIHBhaXJzIHRoYXQgaGFkIGEgNTB0aCBwZXJjZW50aWxlIHRyYXZlbCB0aW1lID4gMzAgbWluIGFuZCBhIDEwdGggcGVyY2VudGlsZSB0cmF2ZWwgdGltZSA8PSAzMCBtaW46DQoNCmBgYHtyfQ0KVFRNIDwtIG11dGF0ZShUVE0sdHRfZGlmZiA9IHRyYXZlbF90aW1lX3A1MCAtIHRyYXZlbF90aW1lX3AxMCkNCg0KcHJpbnQocm91bmQoc3VtKGlzLm5hKFRUTSR0dF9kaWZmKSkgLyBsZW5ndGgoVFRNJHR0X2RpZmYpLDEpKQ0KYGBgDQoNCkZvciB0aG9zZSB3aXRoIGEgMTB0aCBhbmQgNTB0aCBwZXJjZW50aWxlIHRyYXZlbCB0aW1lIDw9IDMwIG1pbiwgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB0d28gaXMgYXMgZm9sbG93czoNCmBgYHtyfQ0KVFRNJHR0X2RpZmYlPiVuYS5vbWl0KCklPiVzdW1tYXJ5KCkNCg0KVFRNPC1UVE0lPiVzZWxlY3QoLXR0X2RpZmYpDQpgYGANCg0KTGV0J3MgZ2V0IHRoZSB0cmF2ZWwgdGltZSBtYXRyaXggcmVhZHkgZm9yIGFuIGV4cG9ydC4gV2Ugd2lsbCByZXN0cnVjdHVyZSB0aGUgZGF0YSBzbyB0aGF0IHRoZSBnZW9tZXRyeSBvZiB0aGUgZGVzdGluYXRpb24gc3RvcHMgKGB0b19pZGApIGlzIGFkZGVkIHRvIGVhY2ggcm93LiBXZSB3aWxsIGFsc28gYWRkIHRoZSBuYW1lIG9mIHRoZSBvcmlnaW4gc3RhdGlvbiBzbyB0aGF0IHRoZSBkYXRhIGlzIGVhc2llciB0byB3b3JrIHdpdGguIFRoaXMgaXMgYSBzaW1pbGFyIG9wZXJhdGlvbiB0byBhIFZMT09LVVAgaW4gZXhjZWwuIFdlIHVzZSB0aGUgYGZyb21faWRgIGFuZCBgdG9faWRgIGZpZWxkcyBhcyB0aGV5IHJlcHJlc2VudCB0aGUgYHBhcmVudF9zdGF0aW9uYC4gDQoNCg0KRm9yIG1vcmUgZ3VpZGFuY2Ugb24gYGRwbHlyYCBmdW5jdGlvbnMgKGxpa2UgYGxlZnRfam9pbigpYCkgY2hlY2sgb3V0IHRoaXMgW2NoZWF0IHNoZWV0XShodHRwczovL255dS1jZHNjLmdpdGh1Yi5pby9sZWFybmluZ3IvYXNzZXRzL2RhdGEtdHJhbnNmb3JtYXRpb24ucGRmKQ0KDQpgYGB7cn0NCiMgam9pbiB0aGUgZ2VvbWV0cnkgb2YgdGhlIGRlc3RpbmF0aW9uIHN0YXRpb24NCmdlb21ldHJ5X3RvX2pvaW4gPC0gc2VsZWN0KHN0b3BzLGlkKSAjIGRyb3AgdW5uZWNlc3NhcnkgY29scw0KZ2VvbWV0cnlfdG9fam9pbiA8LSByZW5hbWUoZ2VvbWV0cnlfdG9fam9pbix0b19pZCA9ICJpZCIpICMgcmVuYW1lICJpZCIgdG8gInRvX2lkIiBzbyB0aGF0IHRoZSBkYXRhIGlzIGpvaW5lZCBhcyBleHBlY3RlZA0KDQpUVE0gPC0NCiAgbGVmdF9qb2luKFRUTSwgDQogICAgICAgICAgICBnZW9tZXRyeV90b19qb2luLA0KICAgICAgICAgICAgYnkgPSAidG9faWQiKQ0KDQoNCiMgam9pbiB0aGUgc3RhdGlvbiBuYW1lIG9mIHRoZSBvcmlnaW4gc3RhdGlvbg0KDQpuYW1lc190b19qb2luIDwtIHNlbGVjdChzdG9wcyxwYXJlbnRfc3RhdGlvbl9uYW1lLGlkKSAjIGRyb3AgdW5uZWNlc3NhcnkgY29scw0KbmFtZXNfdG9fam9pbiA8LSBzdF9kcm9wX2dlb21ldHJ5KG5hbWVzX3RvX2pvaW4pICMgZHJvcCBnZW9tZXRyeQ0KbmFtZXNfdG9fam9pbiA8LSByZW5hbWUobmFtZXNfdG9fam9pbixmcm9tX2lkID0gImlkIikgIyByZW5hbWUgImlkIiB0byAiZnJvbV9pZCIgc28gdGhhdCB0aGUgZGF0YSBpcyBqb2luZWQgYXMgZXhwZWN0ZWQNCg0KDQpUVE0gPC0NCiAgbGVmdF9qb2luKFRUTSwgDQogICAgICAgICAgICBuYW1lc190b19qb2luLA0KICAgICAgICAgICAgYnkgPSAiZnJvbV9pZCIpDQoNClRUTSA8LSBzdF9hc19zZihUVE0pDQoNClRUTQ0KYGBgDQoNClRoaXMgZGF0YSBmb3JtYXQgaXMgZWFzaWVyIHRvIGludGVycHJldCBpZiB3ZSBsb29rIGF0IGEgc2luZ2xlIHN0b3AuIEZvciBleGFtcGxlLCBsZXRzIGZpbmQgYWxsIHN0b3BzIHRoYXQgY2FuIGJlIHJlYWNoZWQgZnJvbSBUaGVyZXNpZW5zdHJhw59lIHdpdGhpbiBhIG1lZGlhbiB0cmF2ZWwgdGltZSBvZiA8PSAyMCBtaW51dGVzLg0KDQpgYGB7cn0NCnRzX3N0b3BzXzIwIDwtIGZpbHRlcihUVE0scGFyZW50X3N0YXRpb25fbmFtZSA9PSAiVGhlcmVzaWVuc3RyYcOfZSIsIHRyYXZlbF90aW1lX3A1MCA8PSAyMCkNCg0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhID0gYXJlYSkrDQogIGdlb21fc2YoZGF0YSA9IHRzX3N0b3BzXzIwLCBjb2xvciA9ICJsaWdodGJsdWUiKSsNCiAgZ2VvbV9zZihkYXRhID0gZmlsdGVyKHN0b3BzLHBhcmVudF9zdGF0aW9uX25hbWUgPT0gIlRoZXJlc2llbnN0cmHDn2UiKSwgY29sb3IgPSAiYmx1ZSIpDQpgYGANCg0KTGV0J3MgZXhwb3J0IHRoaXMgKCYgdGhlIHN0b3BzKSBzbyB0aGF0IHdlIGNhbiB3b3JrIHdpdGggaXQgaW4gUUdJUy4NCg0KYGBge3J9DQpzdF93cml0ZShUVE0sIi4vb3V0cHV0L1RUTS5ncGtnIixhcHBlbmQgPSBGKQ0KDQpzdF93cml0ZShzdG9wcywgIi4vb3V0cHV0L3N0b3BzLmdwa2ciLCBhcHBlbmQgPSBGKQ0KYGBgDQpXZSBjYW4gYWNoaWV2ZSB0aGUgc2FtZSByZXN1bHQgYXMgYWJvdmUgYnkgZG9pbmcgdGhlIGZvbGxvd2luZzoNCg0KIVtdKC4vaW1nL3FnaXNfdHRtLnBuZykNCg0KDQpUaGVuLCBieSB1c2luZyB3aGF0IHlvdSd2ZSBsZWFybmVkIGluIHByZXZpb3VzIHR1dG9yaWFscywgeW91IGNhbiBjcmVhdGUgc2VydmljZSBhcmVhczoNCg0KIVtdKC4vaW1nL3NlcnZpY2VfYXJlYV9leGFtcGxlLnBuZykNCg0KV2UndmUganVzdCBzY3JhdGNoZWQgdGhlIHN1cmZhY2Ugb2Ygd2hhdCB5b3UgY2FuIGRvIHdpdGggYHI1cmAgYW5kIGB0aWR5dHJhbnNpdGAuIENvbnNpZGVyIHRha2luZyBhIGxvb2sgdGhyb3VnaCB0aGUgZG9jdW1lbnRhdGlvbiBmb3Igc29tZSBpbnNwaXJhdGlvbiBmb3IgeW91ciBwcm9qZWN0IQ0KDQoNCg==